TerraformのコードをCircleCI経由でデプロイさせる
はじめに
こんにちは、中山です。
何番煎じか微妙なところではあるのですが、今回TerraformとCircleCIを連携させてみたので一本エントリを書きます。TerraformのコードをGitHubで管理しておき、プッシュしたらCircleCIでTerraform実行というパターンです。
構成図
今回のエントリには直接関係しないのですが、Terraformで作成されるシステムは以下の通りです。一般的なWebシステムによくある構成にしてみました。
コード
GitHubに上げておきました。ご自由にお使いください。
ディレクトリ構造
以下の通りです。
terraform-circleci-demo/ ├── Makefile ├── README.md ├── app.tf ├── bastion.tf ├── circle.yml ├── cloudfront.tf ├── db.tf ├── elasticache.tf ├── elb.tf ├── envs │ ├── dev │ │ ├── main.tf │ │ └── variables.tf │ ├── prd │ │ ├── main.tf │ │ └── variables.tf │ └── stg │ ├── main.tf │ └── variables.tf ├── key_pair.tf ├── keys │ └── site_key.pub ├── modules │ ├── dns │ │ ├── dns.tf │ │ └── variables.tf │ └── iam │ ├── iam.tf │ ├── outputs.tf │ ├── policies │ │ └── ec2_assume_role_policy.json │ └── variables.tf ├── network.tf ├── outputs.tf ├── s3.tf ├── security_group.tf ├── sns.tf ├── user_data │ ├── app_cloud_config.yml │ └── bastion_cloud_config.yml └── variables.tf
ディレクトリ構造の解説については以下のエントリをご覧ください。
コードの解説
circle.yml
machine: environment: S3_BUCKET: terraform-circleci-demo AWS_DEFAULT_REGION: ap-northeast-1 TERRAFORM_VER: 0.7.0 dependencies: override: - | if [[ ! -f ~/.local/bin/terraform ]]; then mkdir -p ~/.local/bin cd ~/.local/bin wget "https://releases.hashicorp.com/terraform/${TERRAFORM_VER}/terraform_${TERRAFORM_VER}_linux_amd64.zip" unzip *.zip rm *.zip fi cache_directories: - ~/.local/bin test: override: - | if [[ "${CIRCLE_BRANCH}" =~ (release/)?dev/? ]]; then make remote-enable ENV=dev make terraform ENV=dev ARGS="get -update" make terraform ENV=dev ARGS=plan elif [[ "${CIRCLE_BRANCH}" =~ (release/)?stg/? ]]; then make remote-enable ENV=stg make terraform ENV=stg ARGS="get -update" make terraform ENV=stg ARGS=plan elif [[ "${CIRCLE_BRANCH}" =~ (release/)?prd/?|master ]]; then make remote-enable ENV=prd make terraform ENV=prd ARGS="get -update" make terraform ENV=prd ARGS=plan fi deployment: development: branch: release/dev commands: - make terraform ENV=dev ARGS="get -update" - make terraform ENV=dev ARGS=apply - make terraform ENV=dev ARGS="remote push" staging: branch: release/stg commands: - make terraform ENV=stg ARGS="get -update" - make terraform ENV=stg ARGS=apply - make terraform ENV=stg ARGS="remote push" production: branch: release/prd commands: - make terraform ENV=prd ARGS="get -update" - make terraform ENV=prd ARGS=apply - make terraform ENV=prd ARGS="remote push"
circle.yml
の詳細についてはドキュメントを参照してください。以下では利用している設定内容について解説します。
machine
環境変数の設定をしています。 circle.yml
内で多用する文字列などをここで定義しておきます。
dependencies
テスト前に必要なソフトウェアをインストールさせる処理を書きます。Terraformが必要なのでそのインストールをしています。
test
テストの処理を記述します。今回は plan
サブコマンドが正常に実行されるかどうかをもとにテストの正常性を判断しています。Remote Stateを有効化しておき、 get -update
でモジュールを最新の状態にします。最後に plan
サブコマンドでテストを実行します。
また、CircleCIは $CIRCLE_BRANCH
でプッシュされたブランチ名を参照可能です。この変数を利用して環境毎にテスト方法を分けています。
deployment
デプロイの処理を記述します。 branch
でブランチ名を、 commands
で実行内容を記述します。 get -update
でTerraformのモジュールを最新の状態にし、 apply
サブコマンドでデプロイ、 remote push
コマンドで変更されたtfstateをS3にアップロードしています。
実行方法
以下では本番環境(prd)を利用する際の手順です。すでにGitHub上に今回のコードが存在し、CircleCIのアカウントがあることを前提としています。
S3バックエンドの有効化
Terraformのtfstateはローカルとリモート(CircleCI)で共有しておく必要があります。このファイルと実際の状態を一致させることで差分を検知できるからです。TerraformにはRemote Stateという方法でリモートのストレージサービスにtfstateを保管することが可能です。今回はS3バックエンドを利用します。
まず、tfstate保存用のバケットを作成します。
# 作成 $ aws s3 mb s3://<bucket-name> # 確認 $ aws s3 ls
続いてMakefileのバケット名の箇所を修正します。 <bucket-name>
を任意のものに変更してください。
BUCKET_NAME = <bucket-name> REGION = ap-northeast-1 CD = [ -d envs/${ENV} ] && cd envs/${ENV} ENV = $1 ARGS = $2 <snip>
また、 circle.yml
でもバケット名を定義しているので、修正する必要があります。
machine: environment: S3_BUCKET: <bucket-name> AWS_DEFAULT_REGION: ap-northeast-1 TERRAFORM_VER: 0.7.0
最後に remote
サブコマンドでRemote Stateを有効化します。コマンドが長いのでMakefileでタスク化しています。
$ make remote-enable ENV=prd Initialized blank state with remote state enabled! Remote state configured and pulled.
上記コマンドを実行すると envs/prd/.terraform
というディレクリが作成され、以降 terraform.tfstate
はそこに移動します。
CircleCIとGitHubを紐付ける
CircleCIにログイン後、GitHub上のリポジトリを紐付けます。
「ADD PROJECTS」をクリック後、自分のGitHubアカウント名をクリックし、該当のリポジトリの「Build project」を選択します。
続いてAWSのクレデンシャルを登録します。「Project Settings」をクリックしてください。
「AWS Permissions」を選択後、「Access Key ID」と「Secret Access Key」を埋めてください。最後に「Save AWS Keys」で保存します。
ブランチの作成
本番環境についてはmasterブランチがテスト用、release/prdブランチがデプロイ用です。事前に両方のブランチを作成しておきます。masterはできていると思うのでリリース用ブランチを作成してください。
$ git branch release/prd $ git branch * master release/prd
コードの修正とプッシュ
準備が整ったので実際にコードを修正してみます。今回本番環境ではアプリサーバを2台ASGで立てていますが、お金がないという体で1台だけにしましょう。該当のファイル envs/prd/variables.tf
を以下のように修正してください。
diff --git a/envs/prd/variables.tf b/envs/prd/variables.tf index b38bf6b..5741df0 100644 --- a/envs/prd/variables.tf +++ b/envs/prd/variables.tf @@ -23,9 +23,9 @@ variable "instance_types" { variable "asg_config" { default = { - "min" = 2 - "max" = 2 - "desired" = 2 + "min" = 1 + "max" = 1 + "desired" = 1 } }
ローカルのmasterブランチでコミット後、リモートのmasterにプッシュします。
$ git add envs/prd/variables.tf $ git commit -m 'Change asg num' [master 4b20190] Change asg num 1 file changed, 3 insertions(+), 3 deletions(-) $ git push origin master
するとGitHubからCircleCIに通知がいき、テストが開始されます。関係ないところでも差分出ていますが、無視してください。。。
masterへのプッシュなのでこの時点ではテストのみ、つまり plan
サブコマンドの実行しかされていません。
デプロイの実施
テストが通ったので最後にデプロイをしてみましょう。ブランチを切り替えてプッシュします。
$ git checkout -b release/prd Switched to a new branch 'release/prd' $ git merge --no-ff master Merge made by the 'recursive' strategy. envs/prd/variables.tf | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) $ git push origin release/prd
デプロイに成功するとCircleCI上で以下のような画面が表示されると思います。
まとめ
いかがでしょうか。
複数人のチームでTerraformのコードを管理する場合はCI経由で実行しないと運用が破綻しそうだなと思いました。今までローカルでの実行が多かったのですが、今後はこういったCIと連携させていこうと思います。
本エントリがみなさんの参考になれば幸いです。
参考リンク
本エントリを書く際に、下記ブログを大変参考にさせていただきました。ありがとうございました。